package Groep1;

import java.util.*;

class NonTerminal extends Element {
    private LinkedList<ProductionRule> productionrules;
    private Set<Terminal> followers;
    private Set<NonTerminal> followersStarters;
    private Set<NonTerminal> followersFollowers;

    public NonTerminal(String name) {
        super(name);
        productionrules = new LinkedList<ProductionRule>();
        followers = new LinkedHashSet<Terminal>();
        followersStarters = new LinkedHashSet<NonTerminal>();
        followersFollowers = new LinkedHashSet<NonTerminal>();
    }

    public void calculateSets() {
        Iterator<ProductionRule> i = productionrules.iterator();
        while(i.hasNext()) {
            ProductionRule rule = i.next();
            rule.calculateStarters(new HashSet<ProductionRule>());
        }
        calculateFollowers();
    }

    public boolean passesRule1() {
        Set<Element> starters=new LinkedHashSet<Element>();
        Iterator<ProductionRule> i = productionrules.iterator();
        System.out.println("Checking Starter sets of "+name+":");
        boolean failed=false;
        while(i.hasNext()) {
            ProductionRule productionrule = i.next();
            Set<Terminal> rulestart = productionrule.getStarters();
            System.out.println(productionrule+" : " +rulestart);
            Set<Terminal> test = new HashSet<Terminal>();
            test.addAll(rulestart);
            test.retainAll(starters);
            test.remove(new Terminal("E"));
            if(test.isEmpty())
                starters.addAll(rulestart);
            else
                failed=true;
        }
        return !failed;
    }

    public Set<Terminal> getStarters() {
        Set<Terminal> starters=new LinkedHashSet<Terminal>();
        Iterator<ProductionRule> i = productionrules.iterator();
        while(i.hasNext()) {
            ProductionRule rule = i.next();
            starters.addAll(rule.getStarters());
        }
        return starters;
    }

    public boolean passesRule2() {
        System.out.println("Checking starters/followers set of "+name);
        Set<Terminal> starters=getStarters();
        Set<Terminal> tempfollowers=getFollowers();
        System.out.println("Starters("+name+") = "+starters);
        System.out.println("Followers("+name+") = "+tempfollowers);
        tempfollowers.retainAll(starters);
        return !(starters.contains(new Terminal("E"))&&!tempfollowers.isEmpty());
    }
    public void addFollowersStarters(NonTerminal nt) {
        followersStarters.add(nt);
    }

    public void addFollowersFollowers(NonTerminal nt) {
        followersFollowers.add(nt);
    }

    public void addFollower(Terminal follower) {
        followers.add(follower);
    }

    public void addProductionRule(LinkedList<Element> rule) {
        productionrules.add(new ProductionRule(this,rule));
    }

    public LinkedList<ProductionRule> getProductionRules() {
        return productionrules;
    }

    public void calculateFollowers() {
        Iterator<ProductionRule> i = productionrules.iterator();
        while(i.hasNext()) {
            ProductionRule rule = i.next();
            LinkedList<Element> elements=rule.getElements();
            if(elements.isEmpty())
                continue;
            Iterator<Element> i2 = elements.iterator();
            LinkedList<NonTerminal> previousnt = new LinkedList<NonTerminal>();
            System.out.println(rule);
            while(i2.hasNext()) {
                Element elem = i2.next();
                // Rule 2
                if(elem instanceof Terminal) {

                    Iterator<NonTerminal> i3 = previousnt.iterator();
                    while(i3.hasNext()) {
                        NonTerminal nt = i3.next();
                        System.out.println("->Followers("+nt.getName()+") contains "+elem+" (Rule 2)");
                        nt.addFollower((Terminal)elem);
                    }
                    previousnt.clear();
                }
                // Rule 4,6
                else if(elem instanceof NonTerminal) {
                    Iterator<NonTerminal> i3 = previousnt.iterator();
                    Set<Terminal> starters = ((NonTerminal)elem).getStarters();
                    while(i3.hasNext()) {
                        NonTerminal nt = i3.next();
                        System.out.println("->Followers("+nt.getName()+") contains Starters("+elem.getName()+") (Rule 4/6)");
                        nt.addFollowersStarters((NonTerminal)elem);
                    }
                    if(starters.contains(new Terminal("E")))
                        previousnt.add((NonTerminal)elem);
                    else {
                        previousnt.clear();
                        previousnt.add((NonTerminal)elem);
                    }
                }
            }
            // Rule 3,5
            ListIterator<Element> li = elements.listIterator(elements.size());
            while(li.hasPrevious()) {
                Element elem = li.previous();
                if(elem instanceof Terminal)
                    break;
                else if(elem instanceof NonTerminal) {
                    NonTerminal nt=(NonTerminal)elem;
                    System.out.println(" -> Followers("+nt.getName()+") contains Followers("+name+") (Rule 3/5)");
                    nt.addFollowersFollowers(this);
                    Set<Terminal> starters = nt.getStarters();
                    if(!starters.contains(new Terminal("E")))
                        break;
                    System.out.println("Starters("+nt.getName()+") contains E. Continueing.");
                }
            }
        }
    }


    public Set<Terminal> getFollowers() {
        Set<NonTerminal> exclude = new HashSet<NonTerminal>();
        return getFollowers(exclude);
    }

    private Set<Terminal> getFollowers(Set<NonTerminal> exclude) {
        //	System.out.println("Getting followers of "+name);
        Set<Terminal> retval = new LinkedHashSet<Terminal>();
        retval.addAll(followers);
        Iterator<NonTerminal> i = followersStarters.iterator();
        while(i.hasNext()) {
            retval.addAll(i.next().getStarters());
        }
        i=followersFollowers.iterator();
        exclude.add(this);
        while(i.hasNext()) {
            NonTerminal nt = i.next();
            if(!exclude.contains(nt)) {
                retval.addAll(nt.getFollowers(exclude));
                exclude.add(nt);
            }
        }
        return retval;
    }

    public String toString() {
        return "NonTerminal: "+name;
    }

}
